/*
 * DevCalc.cpp
 *
 *  Created on: 14.11.2012
 *      Author: fs
 */

#include "GenCalc.h"
#include "CsvLoader.h"
#include <boost/random.hpp>
#include <stdlib.h>
#include <mpi.h>
#include "MPIDataTypes.h"

using namespace std;

GenCalc::GenCalc(data* data, Configuration* conf)
{
	this->datas = data;
	this->bestFittnes = 1000000;
	this->populationSummary = 0;
	this->mutationRate = MUTATION_RATE;
	this->conf = conf;
}

GenCalc::~GenCalc()
{
	for(unsigned int i=0; i<population.size(); i++)
	{
		delete[] population.at(i).gens;
	}

	population.clear();
}

void GenCalc::applyConfigurationState(double* &gens)
{

	if(conf->p != -1)
	{
		gens[0] = conf->p;
	}

	if (conf->c != -1)
	{
		gens[2] = conf->c;
	}
}

void GenCalc::initPopulation()
{
	boost::mt19937 rng;

	boost::uniform_real<float> pRange(0, 1);
	boost::uniform_real<float> cgRange(-1, 0);
	boost::uniform_real<float> cRange(0, 1);
	boost::uniform_real<float> dtRange(conf->dtmin, conf->dtmax);
	boost::uniform_real<float> kRange(conf->kmin, conf->kmax);

	boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen1(rng, pRange);
	boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen2(rng, cgRange);
	boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen3(rng, cRange);
	boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen4(rng, dtRange);

	boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen5(rng, kRange);

	individuum ind;

	for (int i = 0; i < conf->populationCount; i++)
	{
		double* individum = new double[5];

		individum[0] = gen1();		// p
		individum[1] = gen2();		// cg
		individum[2] = gen3();		// c
		individum[3] = gen4();		// dt
		individum[4] = gen5();		// k

		applyConfigurationState(individum);

		ind.gens = individum;
		population.push_back(ind);
	}
}


struct comparator
{
    inline bool operator() (const individuum& struct1, const individuum& struct2)
    {
        return (struct1.fitness < struct2.fitness);
    }
};

int GenCalc::pickIndividual()
{
	double value = populationSummary * (double) rand() / RAND_MAX;
	double sum = 0;


	for (unsigned int i = 0; i < population.size(); i++)
	{
		sum += population.at(i).selectionFitness;
		if (sum >= value)
		{
			return i;
		}
	}
	return rand() % population.size();
}

void GenCalc::crossover(double* result, double* first, double* second)
{
	for (int i = 0; i < 5; i++)
	{
		double p = (double) rand() / RAND_MAX;
			if (p < CROSS_RATE)
			{
				result[i] = (first[i] + second[i]) / 2;
			}
			else
			{
				result[i] = first[i];
			}
	}
}

void GenCalc::mutate(double* individuum)
{
	for (int i = 0; i < 5; i++)
	{
		double ppb = (double) rand() / RAND_MAX;
		if (ppb < MUTATION_RATE)
		{
			individuum[i] = individuum[i] + ppb;
		}
	}
}
double GenCalc::getBestFitness()
{
	return this->bestFittnes;
}


void GenCalc::nextGeneration()
{
		individuum* newPopulation = new individuum[population.size()];

		int size = population.size() /2 ;

		for (int i = 0; i < size; i++)
		{
			//pick two
			int ind1 = pickIndividual();
			int ind2;
			while (ind1 == (ind2 = pickIndividual()));

			newPopulation[i*2].gens = new double[5];
			newPopulation[i*2 + 1].gens = new double[5];

			crossover(newPopulation[i*2].gens, population.at(ind1).gens, population.at(ind2).gens);
			crossover(newPopulation[i*2 +1].gens, population.at(ind2).gens, population.at(ind1).gens);

			mutate(newPopulation[i*2].gens);
			mutate(newPopulation[i*2 + 1].gens);

			applyConfigurationState(newPopulation[i*2].gens);
			applyConfigurationState(newPopulation[i*2+1].gens);
		}


	for(unsigned int i=0; i<population.size(); i++)
	{
		delete[] population.at(i).gens;
	}

	population.clear();

	for(int i=0; i<size; i++)
	{
		population.push_back(newPopulation[i*2]);
		population.push_back(newPopulation[i*2+1]);
	}

	delete[] newPopulation;
}

void GenCalc::writeFittnesAtPosition(double fitness, int index)
{
	population.at(index).fitness = fitness;
}

void GenCalc::nextEvolution(int generation, int size)
{

	MPIDataTypes mpiTypes;

	int sendSize = population.size() / size;

	mpiIndividuum* recieveBuffer = new mpiIndividuum[sendSize];
	mpiIndividuum* populationBuffer = new mpiIndividuum[population.size()];

	double* recivedFitnessBuffer = new double[sendSize];
	double* allFittness = new double[population.size()];

	bool stop = (generation == (conf->generationCount - 1));

	for(unsigned int i=0; i<population.size(); i++)
	{
		populationBuffer[i].p = population.at(i).gens[0];
		populationBuffer[i].cg = population.at(i).gens[1];
		populationBuffer[i].c = population.at(i).gens[2];
		populationBuffer[i].dt = population.at(i).gens[3];
		populationBuffer[i].k = population.at(i).gens[4];
	}

	MPI_Scatter(populationBuffer, sendSize, mpiTypes.mpiIndiviuum, recieveBuffer, sendSize, mpiTypes.mpiIndiviuum, 0, MPI_COMM_WORLD);

	for (int i = 0; i < sendSize; i++)
	{
		recivedFitnessBuffer[i] = fitness(recieveBuffer[i]);
	}

	MPI_Gather(recivedFitnessBuffer, sendSize, MPI_DOUBLE, allFittness, sendSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);


	// nastavime fitness vsem individuum
	for (unsigned int i = 0; i < population.size(); i++)
	{
		population.at(i).fitness = allFittness[i];
	}

	sort(population.begin(), population.end(), comparator());

	populationSummary = 0;
	double value = 100;


	// nastavime hodnotu selekce
	for (unsigned int i = 0; i < population.size(); i++)
	{
		population.at(i).selectionFitness = value;
		populationSummary += population.at(i).selectionFitness;
		value /= DECREASE_RATE;
	}

	if (population.at(0).fitness < this->bestFittnes)
	{
		this->bestFittnes = population.at(0).fitness;
		printf("[%d] : Best values (fitt=%f):  p=%f, cg=%f, c=%f , dt=%f, k=%f \n", generation,
				this->bestFittnes,
				population.at(0).gens[0],
				population.at(0).gens[1],
				population.at(0).gens[2],
				population.at(0).gens[3],
				population.at(0).gens[4]);

	}

	MPI_Bcast(&stop, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD);
}


bool GenCalc::checkTimeCondition(double k, double dt, double ecg)
{
	double minutes = k * dt * ecg * MINUTE;
	if (-5 < minutes || minutes > 5)
	{
		return false;
	}
	return true;
}

double GenCalc::fitness(mpiIndividuum ind)
{
	double res = 0;

	double p = ind.p;
	double cg = ind.cg;
	double c = ind.c;
	double dt = ind.dt;
	double k = ind.k;

	for (int i = 0; i < datas->length; i++)
	{
		double ecg = datas->items[i].ecg;
		if (checkTimeCondition(k, dt, ecg))
		{
			res = WORSE_FITTNES;
			return res;
		}

		double left_side = p * datas->items[i].bg+ cg * datas->items[i].bg * ecg + c;
		double index_time = datas->items[i].time + dt + k * dt * ecg;
		double ig;

		if (this->getIg(index_time * (24 * 60 * 60), &ig) == 1)
		{
			double right_side = ig;
			res += fabs(right_side - left_side);
		}
	}

	return res;
}

int GenCalc::getIg(double time, double *ig)
{
	*ig = 0;

	int t = (int) time;
	int timeStart = this->find_time(t);

	if (timeStart == -1 || timeStart >= datas->length - 1)
	{
		return 0;	 //FALSE
	}

	*ig = datas->items[timeStart].ig + (datas->items[timeStart + 1].ig -  datas->items[timeStart].ig) * (time - t);

	return 1;
}

int GenCalc::find_time(unsigned int time)
{

	int start, end;
	int midIndex = 0;			// binary search
	start = 0;
	end = datas->length;

	while (start <= end)
	{
		midIndex = (start + end) / 2;
		if (datas->items[midIndex].t == time)
		{
			return midIndex;
		}
		else if (datas->items[midIndex].t < time)
		{
			start = midIndex + 1;
		}
		else
		{
			end = midIndex - 1;
		}
	}
	return -1;
}
